﻿using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

using Utils.Http;
using Utils.Weixin.Models;

using static Utils.Weixin.Models.BizLicenseInfo;

namespace Utils.Weixin
{
    /// <summary>
    /// 服务端响应器
    /// </summary>
    public abstract class WeChatService : IDisposable
    {
        public readonly SecretOptions options;

        protected readonly ICacheStrategy cacheStrategy;

        private readonly AsyncLock asyncLock = new AsyncLock(1);
        protected string key => options.ExtData?.Value<string>("Key");

        protected string token => options.ExtData?.Value<string>("Token");

        protected readonly HttpClient httpClient;

        protected readonly string baseUrl = "https://api.weixin.qq.com/";

        public WeChatService(SecretOptions options, ICacheStrategy cacheStrategy)
        {
            this.options = options;
            this.cacheStrategy = cacheStrategy.Change(5);
            this.httpClient = new HttpClient(new DefaultHttpHandler());
        }

        #region AccessToken

        public virtual async Task<(string access_token, int expires_in)> GetAccessToken()
        {
            var jtoken = await SendAsync(HttpMethod.Get, "cgi-bin/token", new
            {
                appid = options.AppId,
                secret = options.AppSecret,
                grant_type = "client_credential"
            }, false);
            return (jtoken.Value<string>("access_token"), jtoken.Value<int>("expires_in"));
        }

        public virtual async Task<string> GetAccessTokenForCache()
        {
            var token = cacheStrategy.Get<string>("token:" + options.AppId);
            if (token.IsNullOrEmpty())
            {
                using var _ = await asyncLock.Lock();
                var jtoken = await GetAccessToken();
                token = jtoken.access_token;
                cacheStrategy.Add("token:" + options.AppId, jtoken.access_token, jtoken.expires_in - 100);
            }
            return token;
        }

        public void ClearToken()
        {
            cacheStrategy.Remove("token:" + options.AppId);
        }
        #endregion


        public async Task<BizLicenseInfo> OcrBizLicense(Stream stream, string fileName)
        {
            MultipartFormDataContent httpContent = new MultipartFormDataContent();
            httpContent.Headers.ContentType = new MediaTypeHeaderValue("multipart/form-data");
            httpContent.Add(new ByteArrayContent(stream.ToBytes()), "\"img\"", "\"" + fileName + "\"");
            var jtoken = await SendAsync(HttpMethod.Post, "cv/ocr/bizlicense", httpContent);
            var info = jtoken.ToObject<BizLicenseInfo>();
            info.position = jtoken["cert_position"]["pos"].ToObject<Position>();
            return info;
        }

        public async Task<IdCardInfo> OcrIdCard(Stream stream, string fileName)
        {
            MultipartFormDataContent httpContent = new MultipartFormDataContent();
            httpContent.Headers.ContentType = new MediaTypeHeaderValue("multipart/form-data");
            httpContent.Add(new ByteArrayContent(stream.ToBytes()), "\"img\"", "\"" + fileName + "\"");
            var jtoken = await SendAsync(HttpMethod.Post, "cv/ocr/idcard", httpContent);
            return jtoken.ToObject<IdCardInfo>();
        }

        public async Task<BankCardInfo> OcrBankcard(Stream stream, string fileName)
        {
            MultipartFormDataContent httpContent = new MultipartFormDataContent();
            httpContent.Headers.ContentType = new MediaTypeHeaderValue("multipart/form-data");
            httpContent.Add(new ByteArrayContent(stream.ToBytes()), "\"img\"", "\"" + fileName + "\"");
            var jtoken = await SendAsync(HttpMethod.Post, "cv/ocr/bankcard", httpContent);
            return jtoken.ToObject<BankCardInfo>();
        }

        /// <summary>
        /// 获取运力id列表
        /// </summary>
        public async Task<Dictionary<string, string>> GetDeliveryList()
        {
            var jtoken = await SendAsync(HttpMethod.Post, "cgi-bin/express/delivery/open_msg/get_delivery_list", new { });
            return jtoken["delivery_list"].ToDictionary(a => a["delivery_id"].ToString(), a => a["delivery_name"].ToString());
        }

        /// <summary>
        /// 提交运动信息进行跟踪
        /// </summary>
        /// <param name="waybillInfo"></param>
        /// <returns>waybill_token 查询物流数据的id</returns>
        public async Task<string> TraceWaybill(TraceWaybillInfo waybillInfo)
        {

            var jtoken = await SendAsync(HttpMethod.Post, "cgi-bin/express/delivery/open_msg/trace_waybill", waybillInfo);
            return jtoken["waybill_token"].ToString();
        }


        #region 回调封装


        public async Task<XmlNode> ReaderBodyToXml(Stream requestBody)
        {
            using var reader = new StreamReader(requestBody, Encoding.UTF8);
            string str = await reader.ReadToEndAsync();
            return LoadXml(str);
        }

        /// <summary>
        /// 微信服务器回调
        /// </summary>
        /// <param name="requestBody"></param>
        /// <param name="query"></param>
        /// <param name="encryptField">密文字段</param>
        /// <returns></returns>
        public XmlNode XmlNotfiy(XmlNode xmlNode, IEnumerable<KeyValuePair<string, StringValues>> query)
        {
            var queryMap = query.ToDictionary(a => a.Key, a => a.Value);
            string signature = queryMap["signature"]; // url
            string timeStamp = queryMap["timestamp"]; // url
            string nonce = queryMap["nonce"]; // url
            string msgSignature = queryMap["msg_signature"];// url
            string encrypt = xmlNode["Encrypt"].InnerText;
            if (msgSignature != WeixinCryptHelper.Signature(token, timeStamp, nonce, encrypt))
            {
                throw Error.DefaultError("签名错误");
            }
            var msg = WeixinCryptHelper.Decrypt(encrypt, key, out string appId);
            var xmlReslt = LoadXml(msg);
            return xmlReslt;
        }

        public XmlNode LoadXml(string xml)
        {
            XmlDocument doc = new XmlDocument();
            doc.XmlResolver = null;
            doc.LoadXml(xml);
            return doc.FirstChild;
        }
        #endregion

        #region 请求封装

        public virtual async Task<JToken> SendAsync(HttpMethod method, string url, HttpContent content, bool beLogin = true)
        {
            if (beLogin)
            {
                url = url.AppendQuery(new
                {
                    access_token = await GetAccessTokenForCache()
                });
            }
            var request = new HttpRequestMessage(method, baseUrl + url);
            request.Content = content;
            return await httpClient.SendAsync(request).ReadAsWxOpenAsync();
        }
        public virtual async Task<JToken> SendAsync(HttpMethod method, string url, object param, bool beLogin = true)
        {
            if (method == HttpMethod.Get)
            {
                url = url.AppendQuery(param);
            }
            if (beLogin)
            {
                url = url.AppendQuery(new
                {
                    access_token = await GetAccessTokenForCache()
                });
            }
            var request = new HttpRequestMessage(method, baseUrl + url);

            if (method != HttpMethod.Get)
                request.Content = new JsonContent(param);
            try
            {
                return await httpClient.SendAsync(request).ReadAsWxOpenAsync();
            }
            catch (ThirdException ex)
            {
                if (ex.Code == "40001")
                {
                    ClearToken();
                }
                else
                {
                    if (param != null)
                    {
                        Console.WriteLine("data：" + param.JSONSerialize());
                    }
                    Console.WriteLine(ex.Code + ":" + ex.Message);
                }

                throw ex;
            }
        }

        public async Task<Stream> SendStreamAsync(HttpMethod method, string url, object param, bool beLogin = true)
        {
            if (method == HttpMethod.Get)
            {
                url = url.AppendQuery(param);
            }
            if (beLogin)
            {
                url = url.AppendQuery(new
                {
                    access_token = await GetAccessTokenForCache()
                });
            }
            var request = new HttpRequestMessage(method, baseUrl + url);
            if (method != HttpMethod.Get)
                request.Content = new JsonContent(param);
            var respone = await httpClient.SendAsync(request);
            if (!respone.IsSuccessStatusCode)
                throw Error.DefaultError("请再试一次");
            if (respone.Content.Headers.ContentType.MediaType.Equals("application/json"))
            {
                var jtoken = await respone.ReadAsJTokenAsync();
                if (jtoken == null || jtoken.Value<int>("errcode") != 0)
                    throw Error.SystemError(jtoken?.Value<string>("errmsg") ?? "微信接口异常");
            }
            return await respone.Content.ReadAsStreamAsync();
        }
        #endregion
        public void Dispose()
        {
            httpClient.Dispose();
            cacheStrategy.CloseChange();
        }
    }
}
